using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using Framework.Library.Core.Utils.Miscellaneous;

namespace Framework.Library.Core.Utils.Xml
{
    public static class XmlUtils
    {
        public static XmlAttribute CreateAttribute(XmlDocument doc, string name, string value)
        {
            ValidationUtils.ArgumentNotNull(doc, "doc");
            ValidationUtils.ArgumentNotNullOrEmpty(name, "name");

            XmlAttribute attribute = doc.CreateAttribute(name);
            attribute.Value = value;

            return attribute;
        }

        /// <summary>
        /// Converts the specified collection to an XmlDocument.
        /// </summary>
        /// <param name="collection">The collection.</param>
        /// <param name="rootName">Name of the root element.</param>
        /// <param name="itemName">Name of the item elements.</param>
        /// <param name="propertyConverters">The property converters.</param>
        /// <returns></returns>
        public static XmlDocument ConvertToXml<T>(IEnumerable<T> collection, string rootName, string itemName,
                                                  Dictionary<string, Converter<T, string>> propertyConverters)
        {
            ValidationUtils.ArgumentNotNull(collection, "collection");
            ValidationUtils.ArgumentNotNull(propertyConverters, "propertyConverters");

            ValidationUtils.ArgumentNotNullOrEmpty(rootName, "rootName");
            ValidationUtils.ArgumentNotNullOrEmpty(itemName, "itemName");

            var doc = new XmlDocument();
            XmlElement rootElement = doc.CreateElement(rootName);
            doc.AppendChild(rootElement);

            foreach (T item in collection)
            {
                XmlElement itemElement = doc.CreateElement(itemName);

                foreach (var property in propertyConverters)
                {
                    string attributeName = property.Key;
                    Converter<T, string> converter = property.Value;

                    string value = converter(item);

                    itemElement.SetAttribute(attributeName, value);
                }

                rootElement.AppendChild(itemElement);
            }

            return doc;
        }

        public static string ToString(XmlReader reader)
        {
            return IndentXml(reader, "  ");
        }

        public static string IndentXml(string xml, string indentChars)
        {
            ValidationUtils.ArgumentNotNullOrEmpty(xml, "xml");

            using (XmlReader xmlReader = XmlReader.Create(new StringReader(xml)))
            {
                return IndentXml(xmlReader, indentChars);
            }
        }

        public static string IndentXml(XmlNode node, string indentChars)
        {
            ValidationUtils.ArgumentNotNull(node, "node");

            using (var xmlReader = new XmlNodeReader(node))
            {
                return IndentXml(xmlReader, indentChars);
            }
        }

        public static string IndentXml(XmlReader xmlReader, string indentChars)
        {
            ValidationUtils.ArgumentNotNull(xmlReader, "xmlReader");
            ValidationUtils.ArgumentNotNullOrEmpty(indentChars, "indentChars");

            var sb = new StringBuilder();
            var sw = new StringWriter(sb);

            if (xmlReader.Read())
            {
                var settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.IndentChars = indentChars;
                settings.OmitXmlDeclaration = (xmlReader.NodeType != XmlNodeType.XmlDeclaration);

                using (XmlWriter xmlWriter = XmlWriter.Create(sw, settings))
                {
                    do
                    {
                        xmlWriter.WriteNode(xmlReader, false);
                    } while (xmlReader.Read());
                }
            }

            return sb.ToString();
        }

        public static bool TryIndentXml(string xml, out string indentedXml)
        {
            return TryIndentXml(xml, "  ", out indentedXml);
        }

        public static bool TryIndentXml(string xml, string indentChars, out string indentedXml)
        {
            return MiscellaneousUtils.TryAction(delegate { return IndentXml(xml, indentChars); }, out indentedXml);
        }

        public static XmlDocument ToXmlDocument(object value)
        {
            ValidationUtils.ArgumentNotNull(value, "value");

            return CreateXmlDocument(delegate(XmlWriter writer)
                                         {
                                             var serializer = new XmlSerializer(value.GetType());
                                             serializer.Serialize(writer, value);
                                         });
        }

        public static XmlDocument CreateXmlDocument(Action<XmlWriter> action)
        {
            ValidationUtils.ArgumentNotNull(action, "action");

            var doc = new XmlDocument();
            using (XmlWriter writer = doc.CreateNavigator().AppendChild())
            {
                action(writer);
            }

            return doc;
        }

        public static XmlNode AppendElement(XmlNode node, string newElementName)
        {
            return AppendElement(node, newElementName, null);
        }

        public static XmlNode AppendElement(XmlNode node, string newElementName, string innerValue)
        {
            XmlNode oNode;

            if (node is XmlDocument)
                oNode = node.AppendChild(((XmlDocument) node).CreateElement(newElementName));
            else
                oNode = node.AppendChild(node.OwnerDocument.CreateElement(newElementName));

            if (innerValue != null)
                oNode.AppendChild(node.OwnerDocument.CreateTextNode(innerValue));

            return oNode;
        }

        public static void SetAttribute(XmlNode node, string attributeName, string attributeValue)
        {
            if (node.Attributes[attributeName] != null)
                node.Attributes[attributeName].Value = attributeValue;
            else
                node.Attributes.Append(CreateAttribute(node.OwnerDocument, attributeName, attributeValue));
        }
    }
}